/******************************************************************
 * Copyright (c) 2024 Bocloud Technologies Co., Ltd.
 * installer is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain n copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 ******************************************************************/

package node

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/pkg/errors"

	"gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	"gopkg.openfuyao.cn/bkecommon/security"
	"gopkg.openfuyao.cn/bkecommon/utils"
	"gopkg.openfuyao.cn/bkecommon/utils/net"
)

const (
	WorkerNodeRole       = "node"
	MasterNodeRole       = "master"
	EtcdNodeRole         = "etcd"
	MasterWorkerNodeRole = MasterNodeRole + "/" + WorkerNodeRole
)

type Nodes []v1beta1.Node
type Node v1beta1.Node

type NodesInterface interface {
	Filter(options FilterOptions) Nodes
	Exclude(options FilterOptions) Nodes
	CurrentNode() (v1beta1.Node, error)
	Length() int
	Master() Nodes
	Etcd() Nodes
	Worker() Nodes
	MasterWorker() Nodes
}

func (n Nodes) Master() Nodes {
	master := n.Filter(FilterOptions{"Role": MasterNodeRole})
	masterWorker := n.Filter(FilterOptions{"Role": MasterWorkerNodeRole})
	return append(master, masterWorker...)
}

func (n Nodes) Etcd() Nodes {
	return n.Filter(FilterOptions{"Role": EtcdNodeRole})
}

func (n Nodes) Worker() Nodes {
	return n.Filter(FilterOptions{"Role": WorkerNodeRole})
}

func (n Nodes) MasterWorker() Nodes {
	return n.Filter(FilterOptions{"Role": MasterWorkerNodeRole})
}

func NodeInfo(node v1beta1.Node) string {
	if node.Hostname == "" {
		return node.IP
	}
	if node.IP == "" {
		return node.Hostname
	}
	return fmt.Sprintf("%s/%s", node.Hostname, node.IP)
}

type NodeInterface interface {
	IsMaster() bool
	IsWorker() bool
	IsEtcd() bool
	IsMasterWorker() bool
}

func (node Node) IsMaster() bool {
	return utils.SliceContainsString(node.Role, MasterNodeRole)
}

func (node Node) IsWorker() bool {
	return utils.SliceContainsString(node.Role, WorkerNodeRole)
}

func (node Node) IsEtcd() bool {
	return utils.SliceContainsString(node.Role, EtcdNodeRole)
}

func (node Node) IsMasterWorker() bool {
	return utils.SliceContainsString(node.Role, MasterWorkerNodeRole)
}

// CurrentNode return the node which the ip is the same as the local ip
func (n Nodes) CurrentNode() (Node, error) {
	ips, err := net.GetAllInterfaceIP()
	if err != nil {
		return Node{}, errors.Wrapf(err, "get ips from interface failed")
	}
	for _, ip := range ips {
		for _, node := range n {
			if strings.Contains(ip, node.IP) {
				return Node(node), nil
			}
		}
	}
	return Node{}, errors.New("can not find the current node from bkeConfig nodes")
}

func (n Nodes) Decrypt() Nodes {
	newNodes := Nodes{}
	for _, node := range n {
		password, err := security.AesDecrypt(node.Password)
		// 如果解密失败，直接返回原始数据
		if err != nil {
			newNodes = append(newNodes, node)
			continue
		}
		node.Password = password
		newNodes = append(newNodes, node)
	}
	return newNodes
}

type FilterOptions map[string]string

// Filter used to filter nodes match the optional
// if the node match all options,it will be returned
func (n Nodes) Filter(options FilterOptions) Nodes {
	nodes := Nodes{}
	for _, node := range n {
		refVal := reflect.ValueOf(node)
		have := true
		for key, value := range options {
			field := refVal.FieldByName(key)
			if !field.IsValid() {
				have = false
				continue
			}
			// 对Role单独处理
			if key == "Role" {
				roles := field.Interface().([]string)
				optionRoles := strings.Split(value, ",")
				if utils.SliceContainsSlice(roles, optionRoles) {
					have = true
					continue
				} else {
					have = false
					break
				}
			}
			fieldVal := field.Interface()
			if fieldVal != value {
				have = false
				break
			} else {
				have = true
			}
		}
		if have {
			nodes = append(nodes, node)
		}
	}
	return nodes
}

// Exclude used to exclude nodes not match the optional
// if the node not match all options,it will be returned
func (n Nodes) Exclude(options FilterOptions) Nodes {
	filtered := n.Filter(options)
	nodes := Nodes{}
	for _, node := range n {
		fonud := false
		for i, f := range filtered {
			if node.IP == f.IP && node.Hostname == f.Hostname {
				fonud = true
				filtered = append(filtered[:i], filtered[i+1:]...)
				break
			}
		}
		if !fonud {
			nodes = append(nodes, node)
		}
	}
	return nodes
}

func (n Nodes) Length() int {
	return len(n)
}
